using UnityEngine;
using System.Collections;


/// <summary>
/// 16 Bit Unit Vector. Very efficient way of storing a unit vector with a precision error of
/// 0.63 ± 0.15 square degrees. This data structure has no noise as opposed to unit vectors
/// in euclidean space using 3 single precision (3 * 32bits = 96bits) floating point values
/// (more than 99.99% noise). This data structure also has precisions much more evenly distributed,
/// as opposed to using eular angles where the poles have most precision.
/// This data structure is mainly designed for networking, where data efficiency is important,
/// but not the speed of computability. Use this structure for sending and storing unit vectors.
/// </summary>
namespace DanielSig.Geometry
{
	public struct UnitVector16 : I16Bit
	{
		#region operators
		public static Vector3 operator *(UnitVector16 a, float b)
		{
			return a.vector * b;
		}
		public static Vector3 operator *(float a, UnitVector16 b)
		{
			return b.vector * a;
		}
		public static float operator *(UnitVector16 a, UnitVector16 b)
		{
			return Vector3.Dot(a.vector, b.vector);
		}
		public static float operator *(UnitVector16 a, Vector3 b)
		{
			return Vector3.Dot(a.vector, b);
		}
		public static float operator *(Vector3 a, UnitVector16 b)
		{
			return Vector3.Dot(a, b.vector);
		}
		
		public static Vector3 operator /(UnitVector16 a, float b)
		{
			return a.vector / b;
		}
		public static Vector3 operator /(float a, UnitVector16 b)
		{
			return b.vector / a;
		}
		
		public static Vector3 operator +(UnitVector16 a, UnitVector16 b)
		{
			return a.vector + b.vector;
		}
		public static Vector3 operator -(UnitVector16 a, UnitVector16 b)
		{
			return a.vector - b.vector;
		}
		public static Vector3 operator +(UnitVector16 a, Vector3 b)
		{
			return a.vector + b;
		}
		public static Vector3 operator -(UnitVector16 a, Vector3 b)
		{
			return a.vector - b;
		}
		public static Vector3 operator +(Vector3 a, UnitVector16 b)
		{
			return a + b.vector;
		}
		public static Vector3 operator -(Vector3 a, UnitVector16 b)
		{
			return a - b.vector;
		}
		#endregion
		
		#region type casting
		public static implicit operator Vector3(UnitVector16 unitVector)
		{
			return unitVector.vector;
		}
		public static implicit operator ushort(UnitVector16 unitVector)
		{
			return unitVector._data;
		}
		public static implicit operator UnitVector16(ushort data)
		{
			return new UnitVector16(data);
		}
		public static explicit operator UnitVector16(Vector3 vector)
		{
			return new UnitVector16(vector);
		}
		#endregion
		
		#region constructs
		public UnitVector16(Vector3 vector)
		{
			_data = 0;
			this.vector = vector;
		}
		public UnitVector16(ushort data)
		{
			_data = data;
		}
		#endregion
		
		#region properties
		public Vector3 vector
		{
			get
			{
				switch(_data)
				{
					case BACK:
						return Vector3.back;
					case FORWARD:
						return Vector3.forward;
					case LEFT:
						return Vector3.left;
					case RIGHT:
						return Vector3.right;
					case UP:
						return Vector3.up;
					case DOWN:
						return Vector3.down;
				}
				
				float u = uValue;
				float v = vValue;
				bool back = ((_data & U_VALUE) >> 7) + (_data & V_VALUE) < V_VALUE;
				if(!back)
				{
					float temp = u;
					u = 1F - v;
					v = 1F - temp;
				}
				Vector3 start = back ? Vector3.back : Vector3.forward;
				Vector3 uDir;
				Vector3 vDir;
				if((_data & UP_BIT) > 0)
				{
					if((_data & RIGHT_BIT) > 0)
					{
						uDir = back ? Vector3.up - Vector3.back : Vector3.right - Vector3.forward;
						vDir = back ? Vector3.right - Vector3.back : Vector3.up - Vector3.forward;
						if(back)
						{
							u = (u * ONE_UNIT_LESS) + ONE_UNIT;
							v = (v * ONE_UNIT_LESS) + ONE_UNIT;
						}
						else
						{
							u *= ONE_UNIT_LESS;
							v *= ONE_UNIT_LESS;
						}
					}
					else
					{
						uDir = back ? Vector3.up - Vector3.back : Vector3.left - Vector3.forward;
						vDir = back ? Vector3.left - Vector3.back : Vector3.up - Vector3.forward;
						if(back)
						{
							u += ONE_UNIT;
						}
						else
						{
							u = (u * TWO_UNITS_LESS) + ONE_UNIT;
							v = (v * TWO_UNITS_LESS);
						}
					}
				}
				else if((_data & RIGHT_BIT) > 0)
				{
					uDir = back ? Vector3.down - Vector3.back : Vector3.right - Vector3.forward;
					vDir = back ? Vector3.right - Vector3.back : Vector3.down - Vector3.forward;
					if(back)
					{
						v += ONE_UNIT;
					}
					else
					{
						u = (u * TWO_UNITS_LESS);
						v = (v * TWO_UNITS_LESS) + ONE_UNIT;
					}
				}
				else
				{
					uDir = back ? Vector3.down - Vector3.back : Vector3.left - Vector3.forward;
					vDir = back ? Vector3.left - Vector3.back : Vector3.down - Vector3.forward;
					if(back)
					{
						u = (u * ONE_UNIT_MORE);
						v = (v * ONE_UNIT_MORE);
					}
					else
					{
						u = (u * THREE_UNITS_LESS) + ONE_UNIT;
						v = (v * THREE_UNITS_LESS) + ONE_UNIT;
					}
				}
				return (start + uDir * u + vDir * v).normalized;
			}
			set
			{
				value.Normalize();
				if(value.y == 0F)
				{
					if(value.x == 0F)
					{
						if(value.z <= 0F)	_data = BACK;
						else				_data = FORWARD;
						return;
					}
					else if(value.z == 0F)
					{
						if(value.x < 0F)	_data = LEFT;
						else				_data = RIGHT;
						return;
					}
				}
				else if(value.x == 0F && value.z == 0F)
				{
					if(value.y < 0F)	_data = DOWN;
					else				_data = UP;
					return;
				}
				
				float minVal = 0.0000001F;
				if(value.x == 0F) value.x = value.z >= 0F ? minVal : -minVal;
				if(value.z >= 0F && value.z < HALF_UNIT) value.z = -minVal;
				
				bool back = value.z < 0F;
				
				if(back)
				{
					if(value.y < HALF_UNIT && value.y >= 0F) value.y = -minVal;
					if(value.x < HALF_UNIT && value.x >= 0F) value.x = -minVal;
				}
				else
				{
					if(value.y > -HALF_UNIT && value.y <= 0F) value.y = minVal;
					if(value.x > -HALF_UNIT && value.x <= 0F) value.x = minVal;
				}
				value.Normalize();
				
				Vector3 start = back ? Vector3.back : Vector3.forward;
				Vector3 uDir;
				Vector3 vDir;
				_data = 0;
				if(value.y > 0F)
				{
					_data |= UP_BIT;
					if(value.x > 0F)
					{
						_data |= RIGHT_BIT;
						uDir = back ? Vector3.up - Vector3.back : Vector3.up - Vector3.forward;
						vDir = back ? Vector3.right - Vector3.back : Vector3.right - Vector3.forward;
					}
					else
					{
						uDir = back ? Vector3.up - Vector3.back : Vector3.up - Vector3.forward;
						vDir = back ? Vector3.left - Vector3.back : Vector3.left - Vector3.forward;
					}
				}
				else if(value.x > 0F)
				{
					_data |= RIGHT_BIT;
					uDir = back ? Vector3.down - Vector3.back : Vector3.down - Vector3.forward;
					vDir = back ? Vector3.right - Vector3.back : Vector3.right - Vector3.forward;
				}
				else
				{
					uDir = back ? Vector3.down - Vector3.back : Vector3.down - Vector3.forward;
					vDir = back ? Vector3.left - Vector3.back : Vector3.left - Vector3.forward;
				}
				float uLen = 1F / uDir.magnitude;
				float vLen = 1F / vDir.magnitude;
				Vector3 uNormal = uDir * uLen;
				Vector3 vNormal = vDir * vLen;
				
				Plane p = new Plane(Vector3.Cross(uNormal, vNormal).normalized, start);
				
				float toValue = p.GetDistanceToPoint(value * 5F);
				float toCenter = p.distance;
				if(toValue < 0F) toValue = -toValue;
				if(toCenter < 0F) toCenter = -toCenter;
				Vector3 flat = value * (5F * (toCenter / (toCenter + toValue)));
				
				float v = Mathf.Sqrt(flat.x * flat.x * 2) * vLen;
				Vector3 r = flat - vDir * v;
				float u = (r - start).magnitude * vLen;
				
				if(value.y > 0F)
				{
					if(value.x > 0F)
					{
						if(back)
						{
							u = (u - ONE_UNIT) / ONE_UNIT_LESS;
							v = (v - ONE_UNIT) / ONE_UNIT_LESS;
						}
						else
						{
							u /= ONE_UNIT_LESS;
							v /= ONE_UNIT_LESS;
						}
					}
					else
					{
						if(back)
						{
							u -= ONE_UNIT;
						}
						else
						{
							u = u / TWO_UNITS_LESS;
							v = (v - ONE_UNIT) / TWO_UNITS_LESS;
						}
					}
				}
				else if(value.x > 0F)
				{
					if(back)
					{
						v -= ONE_UNIT;
					}
					else
					{
						u = (u - ONE_UNIT) / TWO_UNITS_LESS;
						v = (v) / TWO_UNITS_LESS;
					}
				}
				else
				{
					if(back)
					{
						u /= ONE_UNIT_MORE;
						v /= ONE_UNIT_MORE;
					}
					else
					{
						u = (u - ONE_UNIT) / THREE_UNITS_LESS;
						v = (v - ONE_UNIT) / THREE_UNITS_LESS;
					}
				}
				
				if(!back)
				{
					if(u + v > 1F)
					{
						float temp = 1F / (u + v);
						u *= temp;
						v *= temp;
					}
					u = 1F - u;
					v = 1F - v;
				}
				
				ushort tempU = (ushort)((ushort)(u * UV_MAX + 0.5F));
				ushort tempV = (ushort)(v * UV_MAX + 0.5F);
				if(tempU > 0x8000) tempU = 0;//prevent underflow
				else if(tempU > UV_MAX) tempU = UV_MAX;//prevent overflow
				if(tempV > 0x8000) tempV = 0;//prevent underflow
				else if(tempV > UV_MAX) tempV = UV_MAX;//prevent overflow
				
				_data |= (ushort)((tempU << 7) | tempV);
				
				if(back && tempU + tempV >= UV_MAX) _data ^= 0x3FFF;//flipping the UV bits
			}
		}
		
		private float uValue
		{
			get
			{
				return ((_data >> 7) & UV_MAX) * INV_UV_MAX;
			}
			set
			{
				_data = (ushort)(_data & (~U_VALUE));
				_data |= (ushort)(((ushort)(value * UV_MAX + 0.5F) << 7) & U_VALUE);
			}
		}
		private float vValue
		{
			get
			{
				return (_data & UV_MAX) * INV_UV_MAX;
			}
			set
			{
				_data = (ushort)(_data & (~V_VALUE));
				_data |= (ushort)((ushort)(value * UV_MAX + 0.5F) & V_VALUE);
			}
		}
	
		public ushort value
		{
			get
			{
				return _data;
			}
			set
			{
				_data = value;
			}
		}
		public ushort rawData
		{
			get
			{
				return _data;
			}
			set
			{
				_data = value;
			}
		}
		#endregion
		
		public override string ToString()
		{
			return vector.ToString();
		}
		
		private ushort _data;
		
		private const ushort FORWARD = 0xFFFF;
		private const ushort BACK = 0x0000;
		private const ushort UP = 0xBF00;
		private const ushort DOWN = 0x3F00;
		private const ushort RIGHT = 0x407E;
		private const ushort LEFT = 0x007E;
		
		/*
		1000 0000 0000 0000
		0100 0000 0000 0000
		0011 1111 1000 0000
		0000 0000 0111 1111
		*/
		private const ushort UP_BIT =    0x8000;
		private const ushort RIGHT_BIT = 0x4000;
		private const ushort U_VALUE =   0x3F80;
		private const ushort V_VALUE =   0x007F;
		private const ushort UV_MAX = V_VALUE;
		private const float INV_UV_MAX = 1F / UV_MAX;
		private const float HALF_UNIT = INV_UV_MAX / 2F;
		private const float ONE_UNIT = INV_UV_MAX;
		private const float TWO_UNITS = 2F * INV_UV_MAX;
		private const float ONE_UNIT_MORE = (UV_MAX + 1F) / UV_MAX;
		private const float ONE_UNIT_LESS = (UV_MAX - 1F) / UV_MAX;
		private const float TWO_UNITS_LESS = (UV_MAX - 2F) / UV_MAX;
		private const float THREE_UNITS_LESS = (UV_MAX - 3F) / UV_MAX;
	}
}